pub mod unsafe_module {
    // The module's other content.

    // Note the lack of the `#[link]` attribute. We’re delegating the responsibility
    // of selecting what to link over to the build script rather than hard-coding
    // it in the source file.
    extern "C" {
        pub fn hello();

        pub fn set_err(message: *const libc::c_char);

        pub fn get_err(buffer: *mut libc::c_char, size: libc::c_uint) -> libc::c_int;
    }

    pub fn report_error_to_ffi<S: Into<String>>(err: S) -> Result<(), std::ffi::NulError> {
        let c_err = std::ffi::CString::new(err.into())?;

        unsafe {
            // SAFETY: calling an FFI whose documentation says the pointer is
            // const, so no modification should occur.
            set_err(c_err.as_ptr());
        }

        Ok(())
        // The lifetime of `c_err`` continues until here.
    }

    pub fn get_error_from_ffi() -> Result<String, std::ffi::IntoStringError> {
        let mut buffer = vec![0u8; 1024];
        unsafe {
            // SAFETY: calling an FFI whose documentation implies
            // that the input need only live as long as the call.
            let written: usize = get_err(buffer.as_mut_ptr() as *mut libc::c_char, 1023) as usize;

            buffer.truncate(written);
        }

        std::ffi::CString::new(buffer).unwrap().into_string()
    }
}

fn main() {}

#[cfg(test)]
mod tests {
    use std::ffi::CString;

    use super::unsafe_module;

    fn assert_set_and_get_err(message: &str, buffer: Vec<u8>) {
        let c_string_message = CString::new(message).unwrap();
        let c_char_ptr: *const libc::c_char = c_string_message.as_ptr();

        unsafe {
            unsafe_module::set_err(c_char_ptr);

            let buffer = buffer.as_slice();
            let written = unsafe_module::get_err(
                buffer.as_ptr() as *mut libc::c_char,
                (message.len() + 1) as libc::c_uint,
            );

            assert_eq!(message.len() as i32, written);
            assert_eq!(message.as_bytes(), &buffer[0..message.len()]);
            assert_eq!([0], buffer[message.len()..]);
        }
    }

    fn assert_report_error_to_ffi_and_get_error_from_ffi(message: &str) {
        let res = unsafe_module::report_error_to_ffi(message);
        assert!(res.is_ok());

        let res = unsafe_module::get_error_from_ffi();
        assert!(res.is_ok());
        assert_eq!(message, res.unwrap());
    }

    #[test]
    fn set_and_get_err_with_empty_text() {
        let buffer: [u8; 1] = [0; 1];
        assert_set_and_get_err("", buffer.to_vec());
    }

    #[test]
    fn set_and_get_err_with_one_char_text() {
        let buffer: [u8; 2] = [0; 2];
        assert_set_and_get_err("x", buffer.to_vec());
    }

    #[test]
    fn set_and_get_err_with_text() {
        let buffer: [u8; 13] = [0; 13];
        assert_set_and_get_err("test message", buffer.to_vec());
    }

    #[test]
    fn report_error_to_ffi_and_get_error_from_ffi_with_empty_text() {
        assert_report_error_to_ffi_and_get_error_from_ffi("");
    }

    #[test]
    fn report_error_to_ffi_and_get_error_from_ffi_with_one_char_text() {
        assert_report_error_to_ffi_and_get_error_from_ffi("x");
    }

    #[test]
    fn report_error_to_ffi_and_get_error_from_ffi_with_text() {
        assert_report_error_to_ffi_and_get_error_from_ffi("test message");
    }
}
